package com.ly.mp.busicen.rule.extention.auto;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.reflection.ParamNameUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.util.StringUtils;

import com.ly.mp.busicen.rule.extention.annotation.XruleData;
import com.ly.mp.busicen.rule.extention.annotation.XruleFlow;
import com.ly.mp.busicen.rule.extention.annotation.XruleFlow.FireMode;
import com.ly.mp.busicen.rule.flow.IFireFlow;
import com.ly.mp.busicen.rule.flow.IFlowResultCtn;



public class XruleFactoryBean <T> implements FactoryBean<T> {

	private static final Logger log = LoggerFactory.getLogger(XruleFactoryBean.class);
	
	final static LocalVariableTableParameterNameDiscoverer lvtpnd = new LocalVariableTableParameterNameDiscoverer();

	String innerClassName;
	Class<?> innerClass;	
	IFireFlow fireFlow;

	public void setInnerClassName(String innerClassName) throws ClassNotFoundException {
		this.innerClassName = innerClassName;
		this.innerClass = Class.forName(innerClassName);
	}	

	public void setFireFlow(IFireFlow fireFlow) {
		this.fireFlow = fireFlow;
	}



	@SuppressWarnings("unchecked")
	@Override
	public T getObject() throws Exception {
		return (T) createExBean(innerClass,fireFlow);
	}

	@Override
	public Class<?> getObjectType() {
		return innerClass;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	public Object createExBean(Class<?> clazz,IFireFlow fireFlow) {
		if (clazz.isInterface()) {
			ClassLoader classLoader = clazz.getClassLoader();
			Class<?>[] interfaces = new Class<?>[] { clazz };
			return Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> {
				log.info("[{}][{}]", proxy.getClass().getName(), method.getName());
				if(method.equals(Object.class.getMethod("toString", new Class<?>[] {}))) {
					log.debug("don't invoke fireFlow");
					return proxy.getClass().getName();
				}				
				return invoke(proxy, method, args);// realtimeProxy.doInvoke(method, args);
			});
		} else {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(clazz);
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setCallback(new MethodInterceptor() {

				@Override
				public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
					Object result = arg3.invokeSuper(arg0, arg2);
					return result;
				}
			});
			return enhancer.create();
		}
	}
	
	private Object invoke(Object proxy, Method method, Object[] args) {
		XruleFlow xruleFlow = method.getDeclaredAnnotation(XruleFlow.class);
		if (xruleFlow==null) {
			String msg = String.format("[%s][%s]未设置XruleFlow", method.getDeclaringClass().getName(),method.getName());
			log.error(msg);
			throw FlowAutoException.create(msg);
		}
		String flow = xruleFlow.flow();
		Map<String, Object> data = new HashMap<String, Object>();
		Parameter[] parameters = method.getParameters();
		for(int i=0;i<parameters.length;i++) {
			String name="";
			XruleData xruleData = parameters[i].getDeclaredAnnotation(XruleData.class);
			if (xruleData==null||StringUtils.isEmpty(xruleData.value())) {
				name = ParamNameUtil.getParamNames(method).get(i);
			}else {
				name=xruleData.value();
			}
			data.put(name, args[i]);
		}
		IFlowResultCtn resultCtn = xruleFlow.fireMode()==FireMode.NORMAL? fireFlow.fire(flow, data):fireFlow.fireExcpt(flow, data);
		Class<?> returnType = method.getReturnType();
		if (returnType.isInstance(resultCtn)) {
			return resultCtn;
		}
		if (returnType.equals(Void.TYPE)) {
			return null;
		}
		return resultCtn.exitResult();
	}
	
	

}
